package top.fullj.spi;

import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.fullj.func.Condition;
import top.fullj.func.Consumer;
import top.fullj.func.Supplier;

import java.util.*;

/**
 * @author bruce.wu
 * @since 2021/6/9 15:41
 */
@SuppressWarnings({"unused", "WeakerAccess"})
public abstract class Discover {

    private static final Logger LOG = LoggerFactory.getLogger(Discover.class);

    public static <T> T just(Class<T> type) {
        return just(type, new MatchAll<>());
    }

    public static <T> T just(Class<T> type, Condition<T, Boolean> cond) {
        List<T> all = list(type, cond);
        if (all.size() != 1) {
            throw new DiscoverException("Found " + all.size() + " service:" + type.getName());
        }
        return all.get(0);
    }

    public static <T> T firstOrNull(Class<T> type) {
        return firstOrNull(type, new MatchAll<>());
    }

    public static <T> T firstOrNull(Class<T> type, Condition<T, Boolean> cond) {
        List<T> all = list(type, cond);
        if (all.isEmpty()) {
            return null;
        }
        return all.get(0);
    }

    public static <T> T firstOr(Class<T> type, Supplier<T> supplier) {
        return firstOr(type, new MatchAll<>(), supplier);
    }

    public static <T> T firstOr(Class<T> type, Condition<T, Boolean> cond, Supplier<T> supplier) {
        for (T instance : ServiceLoader.load(type)) {
            if (cond.match(instance)) {
                return instance;
            }
        }
        return supplier.get();
    }

    public static <T> T first(Class<T> type) {
        return first(type, new MatchAll<>());
    }

    public static <T> T first(Class<T> type, Condition<T, Boolean> cond) {
        List<T> all = list(type, cond);
        if (all.isEmpty()) {
            throw new DiscoverException("No service provide: " + type.getName());
        }
        return all.get(0);
    }

    public static <T> List<T> list(Class<T> type) {
        return list(type, new MatchAll<>());
    }

    public static <T> List<T> list(Class<T> type, Condition<T, Boolean> cond) {
        Preconditions.checkNotNull(type);
        Preconditions.checkNotNull(cond);
        Iterator<T> it = ServiceLoader.load(type).iterator();
        List<T> list = new LinkedList<>();
        while (it.hasNext()) {
            T instance = it.next();
            if (cond.match(instance)) {
                LOG.debug("SPI load: {}", instance.getClass().getName());
                list.add(instance);
            } else {
                LOG.debug("SPI miss: {}", instance.getClass().getName());
            }
        }
        return Collections.unmodifiableList(list);
    }

    public static <T> void each(Class<T> type, Consumer<T> consumer) {
        each(type, consumer, new MatchAll<>());
    }

    public static <T> void each(Class<T> type, Consumer<T> consumer, Condition<T, Boolean> cond) {
        List<T> list = list(type, cond);
        for (T item : list) {
            consumer.consume(item);
        }
    }

    private static class MatchAll<T> implements Condition<T, Boolean> {
        @Override
        public Boolean match(T t) {
            return true;
        }
    }

}
